package com.zh.web.aspect;

import com.zh.common.annotation.NeedAuth;
import com.zh.common.annotation.NeedPermission;
import com.zh.common.annotation.NeedRoles;
import com.zh.common.constant.AuthConstant;
import com.zh.common.dto.UserAuthDto;
import com.zh.common.exception.AuthException;
import com.zh.common.exception.BusinessException;
import com.zh.common.properties.JwtProperties;
import com.zh.web.domain.auth.dto.UserDto;
import com.zh.web.domain.auth.entity.Permission;
import com.zh.web.domain.auth.vo.RoleVo;
import com.zh.web.enums.Status;
import com.zh.web.openApi.PermissionClient;
import com.zh.web.openApi.RoleClient;
import com.zh.web.utils.JwtUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
@Order(2)
public class AuthAspect {

    private final JwtUtil jwtUtil;
    private final RoleClient roleClient;
    private final PermissionClient permissionClient;
    private final JwtProperties jwtProperties;
    private Map<String, Integer> roleCode2Name;
    private Map<String, Integer> permissionName2Code;

    @Around("@annotation(auth)")
    public Object authPoint(ProceedingJoinPoint joinPoint, NeedAuth auth) throws Throwable {
        //0.调试模式下跳过
        if (jwtProperties.getDebug()) {
            return joinPoint.proceed();
        }
        //1. 获取请求身份信息
        UserAuthDto authDto = authCheck();
        //2.查询所有权限和角色
        roleCode2Name = roleClient.getRoleName2Code().getData();
        permissionName2Code = permissionClient.getPermissionName2Code().getData();
        //3.获取请求拥有角色权限 转set
        List<RoleVo> roleVoList = roleClient.getRoleByUserId(authDto.getId()).getData();
        Map<String, Integer> hasRoles = roleVoList.stream()
                .filter(roleVo -> Status.ENABLE.equals(roleVo.getStatus()))
                .collect(Collectors.toMap(RoleVo::getRoleName, RoleVo::getRoleCode));
        Map<String, Integer> hasPermissions = new HashMap<>();
        roleVoList.forEach(roleVo -> {
            Map<String, Integer> hashPermission = roleVo.getPermissionList()
                    .stream()
                    .filter(permission -> Status.ENABLE.equals(permission.getStatus()))
                    .collect(Collectors.toMap(Permission::getPermissionName, Permission::getPermissionCode));
            hasPermissions.putAll(hashPermission);
        });
        //4.如果存在管理员或者全部权限直接通行
        if (hasRoles.containsKey(AuthConstant.ADMIN) || hasPermissions.containsKey(AuthConstant.ALL)) {
            return joinPoint.proceed();
        }
        //5. 获取所需角色权限规则并验证 默认通过验证，出现异常则拒绝通过
        NeedRoles[] needRoles = auth.roles();
        NeedPermission[] needPermissions = auth.permissions();
        //6. 校验
        checkRule(hasRoles, needRoles, hasPermissions, needPermissions);
        //7. 通过验证
        return joinPoint.proceed();
    }

    private UserAuthDto authCheck() {
        HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
        String authorization = request.getHeader(jwtProperties.getTokenName());
        UserAuthDto authDto;
        try {
            assert jwtUtil.chickToken(authorization);
            authDto = jwtUtil.parseToken(authorization);
        } catch (Exception e) {
            //未登录
            throw new AuthException("未登录");
        }
        return authDto;
    }

    /**
     * 规则校验,角色组和权限组之间为OR关系 通过其一则通过
     */
    public void checkRule(Map<String, Integer> hasRoles, NeedRoles[] needRoles, Map<String, Integer> hasPermissions, NeedPermission[] needPermissions) throws AuthException {
        boolean checkRoles = checkRoles(hasRoles, needRoles);
        boolean checkPermissions = checkPermissions(hasPermissions, needPermissions);
        /*角色或权限至少一边通过*/
        if (!(checkRoles || checkPermissions)) {
            throw new AuthException("权限不足");
        }
    }

    /**
     * 角色验证
     *
     * @param hasRoles  用户拥有角色
     * @param needRoles 所需角色
     * @return 是否通过验证
     * @throws AuthException 验证异常
     */
    public boolean checkRoles(Map<String, Integer> hasRoles, NeedRoles[] needRoles) throws AuthException {
        /* 每组规则校验结果集合 */
        List<Boolean> passList = new ArrayList<>();
        Set<Integer> roleCodeList = new HashSet<>(hasRoles.values());
        for (NeedRoles needRole : needRoles) {
            /* 本组校验结果 */
            boolean nowPass;
            //从roleCode2Name获取角色名包含于 needRole.value() 的角色代码
            List<Integer> roles = Arrays.
                    stream(needRole.value())
                    .map(roleCode2Name::get)
                    .collect(Collectors.toList());
            switch (needRole.logic()) {
                case AND:
                    nowPass = roleCodeList.containsAll(roles);
                    passList.add(nowPass);
                    break;
                case OR:
                    nowPass = roles.stream().anyMatch(roleCodeList::contains);
                    passList.add(nowPass);
                    break;
                default:
                    throw new BusinessException("角色验证规则错误");
            }
        }
        /* 所有角色组至少有一组通过 */
        return passList.stream().anyMatch(Boolean.TRUE::equals);
    }

    /**
     * 权限验证
     *
     * @param hasPermissions  用户拥有权限
     * @param needPermissions 所需权限
     * @return 是否通过验证
     * @throws AuthException 验证异常
     */
    public boolean checkPermissions(Map<String, Integer> hasPermissions, NeedPermission[] needPermissions) throws AuthException {
        /* 所有权限组至少有一组通过 */
        List<Boolean> passList = new ArrayList<>();
        HashSet<Integer> permissionList = new HashSet<>(hasPermissions.values());
        for (NeedPermission needPermission : needPermissions) {
            /* 本组校验结果 */
            boolean nowPass;
            List<Integer> permissions = Arrays.
                    stream(needPermission.value())
                    .map(permissionName2Code::get)
                    .collect(Collectors.toList());
            switch (needPermission.logic()) {
                case AND:
                    nowPass = permissionList.containsAll(permissions);
                    passList.add(nowPass);
                    break;
                case OR:
                    nowPass = permissions.stream().anyMatch(permissionList::contains);
                    passList.add(nowPass);
                    break;
                default:
                    throw new BusinessException("权限验证规则错误");
            }
        }
        /* 所有权限组组至少有一组通过 */
        return passList.stream().anyMatch(Boolean.TRUE::equals);
    }
}
